package logic.nodes.collision;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import sound.AudioManager;

import logic.nodes.damagable.DamagableNode;
import logic.nodes.lod.LODNode;
import logic.nodes.lod.blocks.AbstractBlock;
import logic.nodes.lod.blocks.Block;
import logic.nodes.lod.blocks.LeafBlock;
import logic.weapons.WeaponFire;
import main.InitGame;

import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.TriMesh;

public abstract class Collision {
	
	protected CollidableNode a, b;
	protected Vector3f aToB;
	
	public Collision(CollidableNode a, CollidableNode b) {
		this.a = a;
		this.b = b;
	}
	
	public boolean process() {
		computeLocations();
		
		if(aToB != null) {
			Vector3f pos = a.getBound().getCenter().add(aToB.mult(a.getSize()));
			AudioManager.get().playSound(getCollisionSound(), pos);
			
			makeImpulse(a, b, aToB.negate());
			makeImpulse(b, a, aToB);
			postProcessing();
			return true;
		}
		
		return false;
	}
	
	protected void postProcessing() {}
	
	protected abstract String getCollisionSound();
	
	protected abstract void computeLocations();
	
	protected void makeImpulse(final CollidableNode node, final CollidableNode other, Vector3f normal) {
		float otherSpeed = 0f;
		float nodeSpeed = 0f;
		if(other.canMove()) otherSpeed = other.getCurrentMovingSpeed();
		if(node.canMove()) nodeSpeed = node.getCurrentMovingSpeed();
		
		if(!(other instanceof WeaponFire)) node.applyDamage(computeCollisionDamage(nodeSpeed, otherSpeed));
		else {
			node.getIngameState().getThreadPool().addPostRunnable(new Runnable() {
				@Override
				public void run() {
					WeaponFire fire = (WeaponFire)other;
					if(node instanceof DamagableNode) {
						DamagableNode damagable = (DamagableNode)node;
						if(InitGame.get().isServer()) InitGame.get().getServerState().handleExplosion(fire, node);
						if(!damagable.isDestroyed()) damagable.receiveDamage(fire);
					}
					if(fire.getWeapon() != null && fire.getWeapon().dealsSplashDamage()) {
						float radius = fire.getWeapon().getSplashDamageRadius();
						Vector3f center = fire.getLocalTranslation();
						applySplashDamage(radius, fire, center, node);
					}
					fire.impact();
				}
			});
		}
		
		if(node.isLarge()) {
			if(!node.canMove()) return;
			if(!other.isLarge() && other.canMove()) return;
		}
		
		float otherMass = other.getMass();
		float nodeMass = node.getMass();
		
		float massSum = nodeMass + otherMass;
		if(massSum == 0f) massSum = 1f;
		
		float newNodeSpeed = ((nodeMass - otherMass) * nodeSpeed + (2 * otherMass * otherSpeed)) / massSum;
		if(newNodeSpeed == 0f) return;
		newNodeSpeed = FastMath.abs(newNodeSpeed);
				
		node.createImpulse(newNodeSpeed, normal);
	}
	
	protected int computeCollisionDamage(float nodeSpeed, float otherSpeed) {
		return (int)((nodeSpeed + otherSpeed) * 7f);
	}
	
	protected void applySplashDamage(float radius, WeaponFire fire, Vector3f center, CollidableNode exception) {
		BoundingSphere sphere = new BoundingSphere(radius, center);
		Collection<DamagableNode> affectedNodes = new ArrayList<DamagableNode>();
		checkBlocks(affectedNodes, exception.getIngameState().getRootBlock(), sphere);
		
		for(DamagableNode node : affectedNodes) {
			if(node == exception || node.isDestroyed()) continue;
			
			float dist = node.getLocalTranslation().distance(center);
			if(dist > radius) continue;
			
			int damage = (int)(dist / radius * fire.getDamage());
			if(damage > 0) node.receiveDamage(fire, damage);
		}
	}
	
	protected void checkBlocks(Collection<DamagableNode> nodes, Block parentBlock, BoundingSphere sphere) {
		checkBlock(parentBlock.getBlockA(), nodes, sphere);
		checkBlock(parentBlock.getBlockB(), nodes, sphere);
	}
	
	protected void checkBlock(AbstractBlock block, Collection<DamagableNode> nodes, BoundingSphere sphere) {
		if(block.getBox().intersects(sphere)) {
			if(block instanceof Block) checkBlocks(nodes, (Block)block, sphere);
			else {
				LeafBlock leafBlock = (LeafBlock)block;
				
				List<CollidableNode> leafNodes = leafBlock.getNoneWeaponFireNodes();
				for(int i = 0; i < leafNodes.size(); i++) {
					CollidableNode node = leafNodes.get(i);
					if(!(node instanceof DamagableNode)) continue;
					
					if(sphere.intersects(node.getBound())) nodes.add((DamagableNode)node);
				}
			}
		}
	}
	
	protected BoundingVolume getCollisionPoint(CollidableNode other, LODNode lodNode) {
		Collection<TriMesh> triMeshes = lodNode.getTriMeshes();
		for(TriMesh mesh : triMeshes) {
			BoundingVolume vol = lodNode.getTriMeshBounding(mesh);
			if(vol == null) continue;
			if(lodNode.canMove()) {
				Vector3f dir = lodNode.getTriMeshDir(mesh);
				vol.setCenter(lodNode.getLocalTranslation().add(dir));
			}
			if(vol.intersects(other.getBound())) return vol;
		}
		return null;
	}
}